// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package number

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

// Test MagmaSub
func TestMagmaSub(t *testing.T) {
	subMagma := MagmaSub[int]()

	tests := []struct {
		name     string
		first    int
		second   int
		expected int
	}{
		{"positive numbers", 10, 3, 7},
		{"negative result", 3, 10, -7},
		{"with zero", 5, 0, 5},
		{"zero minus number", 0, 5, -5},
		{"negative numbers", -5, -3, -2},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := subMagma.Concat(tt.first, tt.second)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test MagmaSub with floats
func TestMagmaSub_Float(t *testing.T) {
	subMagma := MagmaSub[float64]()

	result := subMagma.Concat(10.5, 3.2)
	assert.InDelta(t, 7.3, result, 0.0001)

	result = subMagma.Concat(3.2, 10.5)
	assert.InDelta(t, -7.3, result, 0.0001)
}

// Test MagmaDiv
func TestMagmaDiv(t *testing.T) {
	divMagma := MagmaDiv[int]()

	tests := []struct {
		name     string
		first    int
		second   int
		expected int
	}{
		{"simple division", 10, 2, 5},
		{"division with remainder", 10, 3, 3},
		{"one divided by itself", 5, 5, 1},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := divMagma.Concat(tt.first, tt.second)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test MagmaDiv with floats
func TestMagmaDiv_Float(t *testing.T) {
	divMagma := MagmaDiv[float64]()

	result := divMagma.Concat(10.0, 2.0)
	assert.Equal(t, 5.0, result)

	result = divMagma.Concat(10.0, 3.0)
	assert.InDelta(t, 3.333333, result, 0.0001)

	result = divMagma.Concat(1.0, 2.0)
	assert.Equal(t, 0.5, result)
}

// Test SemigroupSum
func TestSemigroupSum(t *testing.T) {
	sumSemigroup := SemigroupSum[int]()

	tests := []struct {
		name     string
		first    int
		second   int
		expected int
	}{
		{"positive numbers", 5, 3, 8},
		{"with zero", 5, 0, 5},
		{"negative numbers", -5, -3, -8},
		{"mixed signs", 10, -3, 7},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sumSemigroup.Concat(tt.first, tt.second)
			assert.Equal(t, tt.expected, result)
		})
	}

	// Test associativity
	a, b, c := 1, 2, 3
	assert.Equal(t,
		sumSemigroup.Concat(sumSemigroup.Concat(a, b), c),
		sumSemigroup.Concat(a, sumSemigroup.Concat(b, c)),
	)
}

// Test SemigroupSum with floats
func TestSemigroupSum_Float(t *testing.T) {
	sumSemigroup := SemigroupSum[float64]()

	result := sumSemigroup.Concat(3.14, 2.86)
	assert.InDelta(t, 6.0, result, 0.0001)

	result = sumSemigroup.Concat(-1.5, 2.5)
	assert.Equal(t, 1.0, result)
}

// Test SemigroupSum with complex numbers
func TestSemigroupSum_Complex(t *testing.T) {
	sumSemigroup := SemigroupSum[complex128]()

	c1 := complex(1, 2)
	c2 := complex(3, 4)
	result := sumSemigroup.Concat(c1, c2)
	expected := complex(4, 6)
	assert.Equal(t, expected, result)
}

// Test SemigroupProduct
func TestSemigroupProduct(t *testing.T) {
	prodSemigroup := SemigroupProduct[int]()

	tests := []struct {
		name     string
		first    int
		second   int
		expected int
	}{
		{"positive numbers", 5, 3, 15},
		{"with one", 5, 1, 5},
		{"with zero", 5, 0, 0},
		{"negative numbers", -5, -3, 15},
		{"mixed signs", 5, -3, -15},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := prodSemigroup.Concat(tt.first, tt.second)
			assert.Equal(t, tt.expected, result)
		})
	}

	// Test associativity
	a, b, c := 2, 3, 4
	assert.Equal(t,
		prodSemigroup.Concat(prodSemigroup.Concat(a, b), c),
		prodSemigroup.Concat(a, prodSemigroup.Concat(b, c)),
	)
}

// Test SemigroupProduct with floats
func TestSemigroupProduct_Float(t *testing.T) {
	prodSemigroup := SemigroupProduct[float64]()

	result := prodSemigroup.Concat(2.5, 4.0)
	assert.Equal(t, 10.0, result)

	result = prodSemigroup.Concat(0.5, 10.0)
	assert.Equal(t, 5.0, result)
}

// Test MonoidSum
func TestMonoidSum(t *testing.T) {
	sumMonoid := MonoidSum[int]()

	// Test concat
	assert.Equal(t, 8, sumMonoid.Concat(5, 3))
	assert.Equal(t, 0, sumMonoid.Concat(5, -5))

	// Test empty
	assert.Equal(t, 0, sumMonoid.Empty())

	// Test identity laws
	testValues := []int{0, 1, -1, 5, 10, -10, 100}
	for _, x := range testValues {
		// Left identity: Empty() + x = x
		assert.Equal(t, x, sumMonoid.Concat(sumMonoid.Empty(), x),
			"Left identity failed for %d", x)

		// Right identity: x + Empty() = x
		assert.Equal(t, x, sumMonoid.Concat(x, sumMonoid.Empty()),
			"Right identity failed for %d", x)
	}

	// Test associativity
	assert.Equal(t,
		sumMonoid.Concat(sumMonoid.Concat(1, 2), 3),
		sumMonoid.Concat(1, sumMonoid.Concat(2, 3)),
	)
}

// Test MonoidSum with floats
func TestMonoidSum_Float(t *testing.T) {
	sumMonoid := MonoidSum[float64]()

	assert.InDelta(t, 6.0, sumMonoid.Concat(3.14, 2.86), 0.0001)
	assert.Equal(t, 0.0, sumMonoid.Empty())

	// Test identity
	x := 5.5
	assert.Equal(t, x, sumMonoid.Concat(sumMonoid.Empty(), x))
	assert.Equal(t, x, sumMonoid.Concat(x, sumMonoid.Empty()))
}

// Test MonoidProduct
func TestMonoidProduct(t *testing.T) {
	prodMonoid := MonoidProduct[int]()

	// Test concat
	assert.Equal(t, 15, prodMonoid.Concat(5, 3))
	assert.Equal(t, 0, prodMonoid.Concat(5, 0))

	// Test empty
	assert.Equal(t, 1, prodMonoid.Empty())

	// Test identity laws
	testValues := []int{1, 2, 3, 5, 10}
	for _, x := range testValues {
		// Left identity: Empty() * x = x
		assert.Equal(t, x, prodMonoid.Concat(prodMonoid.Empty(), x),
			"Left identity failed for %d", x)

		// Right identity: x * Empty() = x
		assert.Equal(t, x, prodMonoid.Concat(x, prodMonoid.Empty()),
			"Right identity failed for %d", x)
	}

	// Test associativity
	assert.Equal(t,
		prodMonoid.Concat(prodMonoid.Concat(2, 3), 4),
		prodMonoid.Concat(2, prodMonoid.Concat(3, 4)),
	)
}

// Test MonoidProduct with floats
func TestMonoidProduct_Float(t *testing.T) {
	prodMonoid := MonoidProduct[float64]()

	assert.Equal(t, 10.0, prodMonoid.Concat(2.5, 4.0))
	assert.Equal(t, 1.0, prodMonoid.Empty())

	// Test identity
	x := 5.5
	assert.Equal(t, x, prodMonoid.Concat(prodMonoid.Empty(), x))
	assert.Equal(t, x, prodMonoid.Concat(x, prodMonoid.Empty()))
}

// Test Add curried function
func TestAdd(t *testing.T) {
	add5 := Add(5)

	tests := []struct {
		name     string
		input    int
		expected int
	}{
		{"positive", 10, 15},
		{"zero", 0, 5},
		{"negative", -3, 2},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := add5(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Add with floats
func TestAdd_Float(t *testing.T) {
	add2_5 := Add(2.5)

	assert.Equal(t, 7.5, add2_5(5.0))
	assert.Equal(t, 2.5, add2_5(0.0))
	assert.InDelta(t, 5.64, add2_5(3.14), 0.0001)
}

// Test Sub curried function
func TestSub(t *testing.T) {
	sub3 := Sub(3)

	tests := []struct {
		name     string
		input    int
		expected int
	}{
		{"positive result", 10, 7},
		{"zero result", 3, 0},
		{"negative result", 1, -2},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sub3(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Sub with floats
func TestSub_Float(t *testing.T) {
	sub2_5 := Sub(2.5)

	assert.Equal(t, 2.5, sub2_5(5.0))
	assert.Equal(t, -2.5, sub2_5(0.0))
	assert.InDelta(t, 0.64, sub2_5(3.14), 0.0001)
}

// Test Mul curried function
func TestMul(t *testing.T) {
	double := Mul(2)

	tests := []struct {
		name     string
		input    int
		expected int
	}{
		{"positive", 5, 10},
		{"zero", 0, 0},
		{"negative", -3, -6},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := double(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Mul with floats
func TestMul_Float(t *testing.T) {
	triple := Mul(3.0)

	assert.Equal(t, 15.0, triple(5.0))
	assert.Equal(t, 0.0, triple(0.0))
	assert.InDelta(t, 9.42, triple(3.14), 0.0001)
}

// Test Div curried function
func TestDiv(t *testing.T) {
	divBy2 := Div(2)

	tests := []struct {
		name     string
		input    int
		expected int
	}{
		{"even number", 10, 5},
		{"odd number", 9, 4},
		{"zero", 0, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := divBy2(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Div with floats
func TestDiv_Float(t *testing.T) {
	half := Div(2.0)

	assert.Equal(t, 5.0, half(10.0))
	assert.Equal(t, 2.5, half(5.0))
	assert.InDelta(t, 1.57, half(3.14), 0.0001)
}

// Test Inc function
func TestInc(t *testing.T) {
	tests := []struct {
		name     string
		input    int
		expected int
	}{
		{"positive", 5, 6},
		{"zero", 0, 1},
		{"negative", -1, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := Inc(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Inc with floats
func TestInc_Float(t *testing.T) {
	assert.Equal(t, 6.5, Inc(5.5))
	assert.Equal(t, 1.0, Inc(0.0))
	assert.InDelta(t, 4.14, Inc(3.14), 0.0001)
}

// Test Min function
func TestMin(t *testing.T) {
	tests := []struct {
		name     string
		a        int
		b        int
		expected int
	}{
		{"a smaller", 3, 5, 3},
		{"b smaller", 5, 3, 3},
		{"equal", 5, 5, 5},
		{"negative", -5, -3, -5},
		{"mixed signs", -5, 3, -5},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := Min(tt.a, tt.b)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Min with floats
func TestMin_Float(t *testing.T) {
	assert.Equal(t, 2.5, Min(2.5, 7.8))
	assert.Equal(t, 2.5, Min(7.8, 2.5))
	assert.Equal(t, 5.5, Min(5.5, 5.5))
	assert.Equal(t, -3.14, Min(-3.14, 2.71))
}

// Test Max function
func TestMax(t *testing.T) {
	tests := []struct {
		name     string
		a        int
		b        int
		expected int
	}{
		{"a larger", 5, 3, 5},
		{"b larger", 3, 5, 5},
		{"equal", 5, 5, 5},
		{"negative", -5, -3, -3},
		{"mixed signs", -5, 3, 3},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := Max(tt.a, tt.b)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// Test Max with floats
func TestMax_Float(t *testing.T) {
	assert.Equal(t, 7.8, Max(2.5, 7.8))
	assert.Equal(t, 7.8, Max(7.8, 2.5))
	assert.Equal(t, 5.5, Max(5.5, 5.5))
	assert.Equal(t, 2.71, Max(-3.14, 2.71))
}

// Benchmark tests
func BenchmarkMonoidSum(b *testing.B) {
	sumMonoid := MonoidSum[int]()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = sumMonoid.Concat(i, i+1)
	}
}

func BenchmarkMonoidProduct(b *testing.B) {
	prodMonoid := MonoidProduct[int]()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = prodMonoid.Concat(i+1, i+2)
	}
}

func BenchmarkAdd(b *testing.B) {
	add5 := Add(5)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = add5(i)
	}
}

func BenchmarkMin(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = Min(i, i+1)
	}
}

func BenchmarkMax(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = Max(i, i+1)
	}
}
